#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version        : 1.0
# @Creaet Time    : 2021/10/14 22:49 
# @File           : case04.py
# @IDE            : PyCharm
# @desc           : 案例04，依赖注入


"""
依赖注入官方文档：https://fastapi.tiangolo.com/zh/tutorial/dependencies/

官方介绍：
"依赖注入"首先意味着在程序中我们的代码可以声明一些它必须依赖的项：我们称之为dependencies，也就是依赖项。
然后，在实际运行中，FastAPI会把所有需要的依赖项提供给你的代码，称之为"注入"依赖项。

"依赖注入"非常适用于以下使用场景：
1、业务逻辑复用
2、共享数据库连接
3、安全机制、权限校验、角色权限等等
4、其他使用场景
所有上述使用场景，借助于"依赖注入"可以明确的提高代码复用，减少代码重复。
"""

"""
依赖项主要分为：
1. 函数依赖项
2. 类依赖项
3. 子依赖项
4. 路径操作装饰器依赖项
5. 全局依赖项
6. 使用 yield 的依赖和子依赖

依赖项统一使用 fastapi.Depends 声明

依赖注入主要为后面讲解安全机制，共享数据库链接做铺垫。
"""


from fastapi import APIRouter, Response, status, HTTPException, Depends, Header
from typing import List, Optional


# =========================================================5.全局依赖项=================================================


"""
有时，我们要为整个应用 或 整个项目添加依赖项。

通过与定义 路径装饰器依赖项 类似的方式，可以把依赖项添加至整个 FastAPI 项目，或 APIRouter 整个应用。
"""


async def app_verify_token(x_token: str = Header(...)):
    if x_token != "fake-super-secret-token":
        raise HTTPException(status_code=400, detail="X-Token header invalid")


async def app_verify_key(x_key: str = Header(...)):
    if x_key != "fake-super-secret-key":
        raise HTTPException(status_code=400, detail="X-Key header invalid")
    return x_key


case04 = APIRouter(dependencies=[Depends(app_verify_token), Depends(app_verify_key)])


@case04.get(path="/", summary="案例4")
def case04_root():
    return "case04_root"


# =========================================================1.函数依赖项=================================================


async def page_parameters(page: int = 1, limit: int = 10):
    print("函数依赖项 公共执行体")
    return {"page": page, "limit": limit}


@case04.get("/get/books", summary="函数依赖项")
async def get_books(page: dict = Depends(page_parameters)):
    """
    依赖项就是一个函数，且可以使用与 路径操作函数 相同的参数：例如：Path，Query

    依赖项使用 Depends 声明

    这里只能传给 Depends 一个参数。且该参数必须是可调用对象，比如函数。

    接收到新的请求时，FastAPI 执行如下操作：

    - 用正确的参数调用依赖项函数（「可依赖项」）
    - 获取函数返回的结果
    - 把函数返回的结果赋值给路径操作函数的参数
    """
    return page


@case04.get("/get/users", summary="函数依赖项")
async def get_users(page: dict = Depends(page_parameters)):
    return page


# =========================================================2.类依赖项=================================================


class CommonQueryParams:
    def __init__(self, page: int = 1, limit: int = 10):
        self.page = page
        self.limit = limit

    def public(self):
        print("类依赖项 公共执行体")


@case04.get("/get/class/depends", summary="类依赖项")
# async def get_class_depends(commons: CommonQueryParams = Depends(CommonQueryParams)):  # 默认语法
async def get_class_depends(commons: CommonQueryParams = Depends()):
# 也可以省略 Depends 中的参数不写，是一种 FastAPI 提供的一种捷径，会直接使用参数变量的 类型 作为 Depends 的参数
# async def get_class_depends(commons: Depends(CommonQueryParams)):
# 也可以省略声明类型不写，不过这样写编辑器不知道该参数是什么类型，不会有类属性和方法提示，建议使用上面方式声明类型
    """
    在 FastAPI 中，您可以使用 Python 类作为依赖项。

    FastAPI 实际检查的是它是否为『可调用的』（函数、类或者其他任何东西）。

    如果您在 FastAPI 中将『可调用的』作为依赖项传递，它将分析该『可调用的』参数，并以与路径操作函数的参数相同的方式处理它们。包括子依赖项。

    这也适用于根本没有参数的可调用对象。与没有参数的路径操作函数相同。
    """
    commons.public()
    return {"page": commons.page, "limit": commons.limit}


# =========================================================3.子依赖项=================================================


"""
FastAPI 支持创建含子依赖项的依赖项。

并且，可以按需声明任意深度的子依赖项嵌套层级。

FastAPI 负责处理解析不同深度的子依赖项。
"""


department_table = {
    "IT": ["Li", "Feng", "Ding"],
    "UI": ["Fang", "Zhang"]
}


def query_department(department: str):
    return department_table[department]


def query_user(username: str, department: List[str] = Depends(query_department)):
    if username not in department:
        return None

    return username


@case04.get("/son/depends/func", summary="子依赖项")
async def son_depends_func(user: Optional[str] = Depends(query_user)):
    """
    执行逻辑：<br>
    - 用户输入部门，和用户名
    - 根据用户输入的部门，查询出部门下有哪些员工
    - 再判断部门员工中有没有用户输入的用户名，有则返回此员工，无则返回空
    """
    return {"user": user}


@case04.get("/son/depends/func/no/cache", summary="子依赖项，不使用缓存值")
async def son_depends_func_no_cache(user: Optional[str] = Depends(query_user, use_cache=False)):
    """
    如果在同一个路径操作 多次声明了同一个依赖项，例如，多个依赖项共用一个子依赖项，FastAPI 在处理同一请求时，只调用一次该子依赖项。

    FastAPI 不会为同一个请求多次调用同一个依赖项，而是把依赖项的返回值进行「缓存」，并把它传递给同一请求中所有需要使用该返回值的「依赖项」。

    在高级使用场景中，如果不想使用「缓存」值，而是为需要在同一请求的每一步操作（多次）中都实际调用依赖项，可以把 Depends 的参数 use_cache 的值设置为 False
    """
    return {"user": user}


# =========================================================4.路径操作装饰器依赖项=================================================


"""
有时，我们并不需要在路径操作函数中使用依赖项的返回值。

或者说，有些依赖项不返回值。

但仍要执行或解析该依赖项。

对于这种情况，不必在声明路径操作函数的参数时使用 Depends，而是可以在路径操作装饰器中添加一个由 dependencies 组成的 list。
"""


async def verify_token(x_token: str = Header(...)):
    if x_token != "fake-super-secret-token":
        raise HTTPException(status_code=400, detail="X-Token header invalid")


async def verify_key(x_key: str = Header(...)):
    if x_key != "fake-super-secret-key":
        raise HTTPException(status_code=400, detail="X-Key header invalid")
    return x_key


@case04.get("/path/depends/func", dependencies=[Depends(verify_token), Depends(verify_key)], summary="路径操作装饰器依赖项")
async def path_depends_func():
    """
    路径操作装饰器依赖项的执行或解析方式和普通依赖项一样，但就算这些依赖项会返回值，它们的值也不会传递给路径操作函数。

    路径装饰器依赖项与正常的依赖项一样，可以 raise 异常
    """
    return [{"item": "Foo"}, {"item": "Bar"}]


# =========================================================6.使用 yield 的依赖和子依赖====================================


"""
FastAPI 支持在完成后会执行一些额外步骤的依赖项。

为此，请使用 yield 而不是 return，然后再写一些额外的步骤。
"""


class DBSession(object):

    def close(self):
        pass


async def func_get_db():
    """
    例如，您可以使用它来创建数据库会话并在完成后关闭它。

    发送响应之前，仅执行 yield 语句之前的代码（包括该代码）：
    """
    db = DBSession()
    try:
        yield db    # 产生的值是注入到路径操作和其他依赖项中的值
    finally:
        db.close()  # yield响应传递后执行语句后面的代码


"""
在 yield 依赖项中同样可以创建任意深度的子依赖项嵌套层级。并且它们中的任何一个或全部都可以使用 yield。

FastAPI 将确保每个带有 yield 的依赖项中的『退出代码』以正确的顺序运行。

这与 Python 使用 yield 方式一致，不再过多讲解

您可以拥有所需的任何依赖项组合。

FastAPI将确保一切以正确的顺序运行。
"""


class MySuperContextManager:
    """
    Python 中，您可以通过 使用类中的两个方法：__enter__() 和 __exit__() 来创建上下文管理器。
    """
    def __init__(self):
        self.db = DBSession()

    def __enter__(self):
        return self.db

    def __exit__(self, exc_type, exc_value, traceback):
        self.db.close()


async def class_get_db():
    """
    在具有 yield 的依赖项中使用上下文管理器
    """
    with MySuperContextManager() as db:
        yield db


